package sketch.specs;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import randoop.ComponentManager;
import randoop.ExecutableSequence;
import randoop.RConstructor;
import randoop.RMethod;
import randoop.Sequence;
import randoop.StatementKind;
import randoop.Variable;
import randoop.util.Reflection;
import sketch.util.Checker;
import sketch.util.Globals;

public class RandomSequenceCreator {
	
	//the randoop delegate to create random sequences
	static RandoopWrapper randoop = new RandoopWrapper();
	
	//I guess we need to keep some global static state of the field
	private final Class<?> mainClass;
	private final List<StatementKind> statements = new LinkedList<StatementKind>();
	private final Map<Class<?>, List<RConstructor>> class_rconstructor_map = new LinkedHashMap<Class<?>, List<RConstructor>>();
	private final Map<Class<?>, List<RMethod>> class_rmethod_map = new LinkedHashMap<Class<?>, List<RMethod>>();
	
	public RandomSequenceCreator(Class<?> mainClass, Class<?>...others) {
		this.mainClass = mainClass;
		//lookup the statements
		Collection<Class<?>> unloadedClasses = new LinkedList<Class<?>>();
		if(!randoop.classes.contains(mainClass)) {
			unloadedClasses.add(mainClass);
		}
		for(Class<?> clz : others) {
			if(!randoop.classes.contains(clz)) {
				unloadedClasses.add(clz);
			}
		}
		//load all statements
		statements.addAll(Reflection.getStatements(unloadedClasses, null));
		randoop.model.addAll(statements);
		//System.out.println("randoop model after addition: " + randoop.model);
		//do fast classification...
		init_stmt_map();
	}
	
	private void init_stmt_map() {
		for(StatementKind statement : statements) {
			if(statement instanceof RMethod) {
				RMethod rmethod = (RMethod)statement;
				Class<?> decl = rmethod.getMethod().getDeclaringClass();
				if(!class_rmethod_map.containsKey(decl)) {
					class_rmethod_map.put(decl, new LinkedList<RMethod>());
				}
				class_rmethod_map.get(decl).add(rmethod);
			} else if (statement instanceof RConstructor) {
			   RConstructor rconst = (RConstructor)statement;
			   Class<?> decl = rconst.getConstructor().getDeclaringClass();
			   if(!class_rconstructor_map.containsKey(decl)) {
				   class_rconstructor_map.put(decl, new LinkedList<RConstructor>());
			   }
			   class_rconstructor_map.get(decl).add(rconst);
			} else {
				Checker.fail("Unexpected statement kind: " + statement.getClass());
			}
		}
	}
	
	public List<RConstructor> lookup_constructors(Class<?> clazz) {
		if(!class_rconstructor_map.containsKey(clazz)) {
			return new LinkedList<RConstructor>();
		} else {
			return class_rconstructor_map.get(clazz);
		}
	}
	
	public List<RMethod> lookup_rmethods(Class<?> clazz) {
		if(!class_rmethod_map.containsKey(clazz)) {
			return new LinkedList<RMethod>();
		} else {
			return class_rmethod_map.get(clazz);
		}
	}
	
	
	public ComponentManager getComponentManagerInRandoop() {
		return randoop.getComponentManager();
	}
	
	public void generateSequences(int time) {
		randoop.generateSequences(time);
	}
	
	public List<StatementKind> getModelInRandoop() {
		return this.statements;
	}
	
	public List<ExecutableSequence> createAllSequence(int time) {
		//List<Sequence> sequence_list = new LinkedList<Sequence>();
		return randoop.createAllSequences(mainClass, time);
//		return sequence_list;
	}
	
	public List<Sequence> createSequence(int num, int time) {
		List<Sequence> sequence_list = new LinkedList<Sequence>();
		
		Checker.checkTrue(sequence_list.size() == num, "The number is not correct, list size: "
				+ sequence_list.size() + ", but required num: " + num);
		return sequence_list;
	}
	
	public ExecutableSequence createOneSequence(int time) {
		return randoop.createSequence(mainClass, time);
	}
	
	public List<String> getSequenceTxtList(String defVar, int time) {
		List<String> seq_strs = new LinkedList<String>();
		List<ExecutableSequence> sequences = this.createAllSequence(time);
		for(ExecutableSequence eSeq : sequences) {
			Sequence sequence = eSeq.sequence;
			int index = -1;
			for(int i = sequence.size() - 1; i >= 0; i--) {
				StatementKind statement = sequence.getStatementKind(i);
				Class<?> outputType = statement.getOutputType();
				//outputType could be used as mainClass
				if(Reflection.canBeUsedAs(outputType, mainClass)) {
					index = i;
					break;
				}
			}
			Checker.checkTrue(index != -1, "index should not be -1.");
			Variable var = sequence.getVariable(index);
			
			String seq_str = sequence.toCodeString()
			        + /*mainClass.getCanonicalName() + " " +*/  defVar + " = " + var.toString() + ";"
			        + Globals.lineSep;
			
			seq_strs.add(seq_str);
		}
		return seq_strs;
	}
	
	public String getSequenceTxt(String defVar) {
		ExecutableSequence eSeq = this.createOneSequence(1);
		Sequence sequence = eSeq.sequence;
		int index = -1;
		for(int i = sequence.size() - 1; i >= 0; i--) {
			StatementKind statement = sequence.getStatementKind(i);
			Class<?> outputType = statement.getOutputType();
			//outputType could be used as mainClass
			if(Reflection.canBeUsedAs(outputType, mainClass)) {
				index = i;
				break;
			}
		}
		Checker.checkTrue(index != -1, "index should not be -1.");
		Variable var = sequence.getVariable(index);
		
		return sequence.toCodeString()
		        + /*mainClass.getCanonicalName() + " " +*/  defVar + " = " + var.toString() + ";"
		        + Globals.lineSep; 
	}
}